package com.rrd.spring.event.scanner;

import com.rrd.spring.event.EventHandler;
import com.rrd.spring.event.router.EventRouter;
import com.rrd.spring.event.router.Invoker;
import com.rrd.spring.event.router.ReflectionInvoker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationEvent;

import java.lang.reflect.Method;

/**
 * A {@link BeanFactoryPostProcessor} that scans every instantiated bean for {@link EventHandler} annotations and
 * registers a router
 * that routes concrete events to the event handling methods.
 *
 * @author eugene_rachitskiy
 * @see EventHandler
 */
public class EventHandlerBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    private static final Logger logger = LoggerFactory
            .getLogger(EventHandlerBeanFactoryPostProcessor.class);

    public void postProcessBeanFactory(final ConfigurableListableBeanFactory cbf)
            throws BeansException {
        ((BeanDefinitionRegistry) cbf).registerBeanDefinition(EventRouter.class.getName(), new RootBeanDefinition(EventRouter.class));
        cbf.addBeanPostProcessor(new BeanPostProcessor() {
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                return bean;
            }

            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean == null) {
                    return null;
                }
                Class<?> beanClass = bean.getClass();
                for (final Method method : beanClass.getDeclaredMethods()) {
                    EventHandler eventHandlerAnnotation = method.getAnnotation(EventHandler.class);
                    if (eventHandlerAnnotation != null) {
                        if (method.getParameterTypes() == null || method.getParameterTypes().length != 1 || !ApplicationEvent.class.isAssignableFrom(method.getParameterTypes()[0])) {
                            logger.error("Bean " + beanName + " of type " + beanClass.getSimpleName() + " is annotated with " + EventHandler.class.getSimpleName() + " but does not have a single concrete ApplicationContextEvent parameter.");
                        } else {
                            Class<?> eventType = method.getParameterTypes()[0];
                            Invoker invoker = new ReflectionInvoker(bean, method);
                            cbf.getBean(EventRouter.class.getName(), EventRouter.class).getInvokerMap().get(eventType).add(invoker);
                        }
                    }
                }
                return bean;
            }
        });
    }
}